/***************************************************************************
 *   Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License (LGPL)   *
 *   as published by the Free Software Foundation; either version 2 of     *
 *   the License, or (at your option) any later version.                   *
 *   for detail see the LICENCE text file.                                 *
 *                                                                         *
 *   FreeCAD is distributed in the hope that it will be useful,            *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with FreeCAD; if not, write to the Free Software        *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
 *   USA                                                                   *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"

#include "Exception.h"
#include "Console.h"
#include "PyObjectBase.h"


FC_LOG_LEVEL_INIT("Exception", true, true)

using namespace Base;


TYPESYSTEM_SOURCE(Base::Exception, Base::BaseClass)


Exception::Exception()
    : _sErrMsg("FreeCAD Exception")
    , _line(0)
    , _isTranslatable(false)
    , _isReported(false)
{}

Exception::Exception(const Exception& inst) = default;

Exception::Exception(Exception&& inst) noexcept = default;

Exception::Exception(const char* sMessage)
    : _sErrMsg(sMessage)
    , _line(0)
    , _isTranslatable(false)
    , _isReported(false)
{}

Exception::Exception(std::string sMessage)
    : _sErrMsg(std::move(sMessage))
    , _line(0)
    , _isTranslatable(false)
    , _isReported(false)
{}

Exception& Exception::operator=(const Exception& inst)
{
    _sErrMsg = inst._sErrMsg;
    _file = inst._file;
    _line = inst._line;
    _function = inst._function;
    _isTranslatable = inst._isTranslatable;
    return *this;
}

Exception& Exception::operator=(Exception&& inst) noexcept
{
    _sErrMsg = std::move(inst._sErrMsg);
    _file = std::move(inst._file);
    _line = inst._line;
    _function = std::move(inst._function);
    _isTranslatable = inst._isTranslatable;
    return *this;
}

const char* Exception::what() const noexcept
{
    return _sErrMsg.c_str();
}

void Exception::ReportException() const
{
    if (!_isReported) {
        const char* msg {};
        if (_sErrMsg.empty()) {
            msg = typeid(*this).name();
        }
        else {
            msg = _sErrMsg.c_str();
        }
#ifdef FC_DEBUG
        if (!_function.empty()) {
            _FC_ERR(_file.c_str(), _line, _function << " -- " << msg);
        }
        else
#endif
        {
            _FC_ERR(_file.c_str(), _line, msg);
        }
        _isReported = true;
    }
}

PyObject* Exception::getPyObject()
{
    Py::Dict edict;
    edict.setItem("sclassname", Py::String(typeid(*this).name()));
    edict.setItem("sErrMsg", Py::String(this->getMessage()));
    edict.setItem("sfile", Py::String(this->getFile()));
    edict.setItem("iline", Py::Long(this->getLine()));
    edict.setItem("sfunction", Py::String(this->getFunction()));
    edict.setItem("swhat", Py::String(this->what()));
    edict.setItem("btranslatable", Py::Boolean(this->getTranslatable()));
    edict.setItem("breported", Py::Boolean(this->_isReported));
    return Py::new_reference_to(edict);
}

void Exception::setPyObject(PyObject* pydict)
{
    try {
        if (pydict && Py::_Dict_Check(pydict)) {
            Py::Dict edict(pydict);
            if (edict.hasKey("sfile")) {
                _file = static_cast<std::string>(Py::String(edict.getItem("sfile")));
            }

            if (edict.hasKey("sfunction")) {
                _function = static_cast<std::string>(Py::String(edict.getItem("sfunction")));
            }

            if (edict.hasKey("sErrMsg")) {
                _sErrMsg = static_cast<std::string>(Py::String(edict.getItem("sErrMsg")));
            }

            if (edict.hasKey("iline")) {
                _line = static_cast<long>(Py::Long(edict.getItem("iline")));
            }
            if (edict.hasKey("btranslatable")) {
                _isTranslatable = static_cast<bool>(Py::Boolean(edict.getItem("btranslatable")));
            }
            if (edict.hasKey("breported")) {
                _isReported = static_cast<bool>(Py::Boolean(edict.getItem("breported")));
            }
        }
    }
    catch (Py::Exception& e) {
        e.clear();  // should never happen
    }
}

PyObject* Exception::getPyExceptionType() const
{
    return PyExc_FC_GeneralError;
}

void Exception::setPyException() const
{
    PyObject* exc = getPyExceptionType();
    if (!exc) {
        exc = PyExc_FC_GeneralError;
    }

    PyErr_SetString(exc, what());
}

// ---------------------------------------------------------

TYPESYSTEM_SOURCE(Base::AbortException, Base::Exception)

AbortException::AbortException(const char* sMessage)
    : Exception(sMessage)
{}

AbortException::AbortException()
{
    _sErrMsg = "Aborted operation";
}

const char* AbortException::what() const noexcept
{
    return Exception::what();
}

PyObject* AbortException::getPyExceptionType() const
{
    return PyExc_FC_FreeCADAbort;
}

// ---------------------------------------------------------


XMLBaseException::XMLBaseException() = default;

XMLBaseException::XMLBaseException(const char* sMessage)
    : Exception(sMessage)
{}

XMLBaseException::XMLBaseException(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* XMLBaseException::getPyExceptionType() const
{
    return PyExc_FC_XMLBaseException;
}

// ---------------------------------------------------------

XMLParseException::XMLParseException(const char* sMessage)
    : XMLBaseException(sMessage)
{}

XMLParseException::XMLParseException(const std::string& sMessage)
    : XMLBaseException(sMessage)
{}

XMLParseException::XMLParseException()
{
    _sErrMsg = "XML parse exception";
}

const char* XMLParseException::what() const noexcept
{
    return XMLBaseException::what();
}

PyObject* XMLParseException::getPyExceptionType() const
{
    return PyExc_FC_XMLParseException;
}

// ---------------------------------------------------------

XMLAttributeError::XMLAttributeError(const char* sMessage)
    : XMLBaseException(sMessage)
{}

XMLAttributeError::XMLAttributeError(const std::string& sMessage)
    : XMLBaseException(sMessage)
{}

XMLAttributeError::XMLAttributeError()
{
    _sErrMsg = "XML attribute error";
}

const char* XMLAttributeError::what() const noexcept
{
    return XMLBaseException::what();
}

PyObject* XMLAttributeError::getPyExceptionType() const
{
    return PyExc_FC_XMLAttributeError;
}

// ---------------------------------------------------------


FileException::FileException(const char* sMessage, const char* sFileName)
    : Exception(sMessage)
    , file(sFileName)
{
    setFileName(sFileName);
}

FileException::FileException(const char* sMessage, const FileInfo& File)
    : Exception(sMessage)
    , file(File)
{
    setFileName(File.filePath().c_str());
}

FileException::FileException()
    : Exception("Unknown file exception happened")
    , _sErrMsgAndFileName(_sErrMsg)
{}

void FileException::setFileName(const char* sFileName)
{
    file.setFile(sFileName);
    _sErrMsgAndFileName = _sErrMsg;
    if (sFileName) {
        _sErrMsgAndFileName += ": ";
        _sErrMsgAndFileName += sFileName;
    }
}

std::string FileException::getFileName() const
{
    return file.fileName();
}

const char* FileException::what() const noexcept
{
    return _sErrMsgAndFileName.c_str();
}

void FileException::ReportException() const
{
    if (!_isReported) {
        const char* msg {};
        if (_sErrMsgAndFileName.empty()) {
            msg = typeid(*this).name();
        }
        else {
            msg = _sErrMsgAndFileName.c_str();
        }
#ifdef FC_DEBUG
        if (!_function.empty()) {
            _FC_ERR(_file.c_str(), _line, _function << " -- " << msg);
        }
        else
#endif
        {
            _FC_ERR(_file.c_str(), _line, msg);
        }
        _isReported = true;
    }
}

PyObject* FileException::getPyObject()
{
    Py::Dict edict(Exception::getPyObject(), true);
    edict.setItem("filename", Py::String(this->file.fileName()));
    return Py::new_reference_to(edict);
}

void FileException::setPyObject(PyObject* pydict)
{
    if (pydict) {
        Exception::setPyObject(pydict);

        Py::Dict edict(pydict);
        if (edict.hasKey("filename")) {
            setFileName(Py::String(edict.getItem("filename")).as_std_string("utf-8").c_str());
        }
    }
}

PyObject* FileException::getPyExceptionType() const
{
    return PyExc_IOError;
}

// ---------------------------------------------------------


FileSystemError::FileSystemError() = default;

FileSystemError::FileSystemError(const char* sMessage)
    : Exception(sMessage)
{}

FileSystemError::FileSystemError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* FileSystemError::getPyExceptionType() const
{
    return PyExc_IOError;
}

// ---------------------------------------------------------


BadFormatError::BadFormatError() = default;

BadFormatError::BadFormatError(const char* sMessage)
    : Exception(sMessage)
{}

BadFormatError::BadFormatError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* BadFormatError::getPyExceptionType() const
{
    return PyExc_FC_BadFormatError;
}

// ---------------------------------------------------------


MemoryException::MemoryException()
{
    _sErrMsg = "Not enough memory available";
}

MemoryException::MemoryException(const MemoryException& inst)
#if defined(__GNUC__)
    : std::bad_alloc()
    , Exception(inst)
#else
    : Exception(inst)
#endif
{}

MemoryException::MemoryException(MemoryException&& inst) noexcept
#if defined(__GNUC__)
    : std::bad_alloc()
    , Exception(inst)
#else
    : Exception(inst)
#endif
{}

MemoryException& MemoryException::operator=(const MemoryException& inst)
{
    Exception::operator=(inst);
    return *this;
}

MemoryException& MemoryException::operator=(MemoryException&& inst) noexcept
{
    Exception::operator=(inst);
    return *this;
}

#if defined(__GNUC__)
const char* MemoryException::what() const noexcept
{
    // call what() of Exception, not of std::bad_alloc
    return Exception::what();
}
#endif

PyObject* MemoryException::getPyExceptionType() const
{
    return PyExc_MemoryError;
}

// ---------------------------------------------------------

AccessViolation::AccessViolation()
{
    _sErrMsg = "Access violation";
}

AccessViolation::AccessViolation(const char* sMessage)
    : Exception(sMessage)
{}

AccessViolation::AccessViolation(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* AccessViolation::getPyExceptionType() const
{
    return PyExc_OSError;
}

// ---------------------------------------------------------

AbnormalProgramTermination::AbnormalProgramTermination()
{
    _sErrMsg = "Abnormal program termination";
}

AbnormalProgramTermination::AbnormalProgramTermination(const char* sMessage)
    : Exception(sMessage)
{}

AbnormalProgramTermination::AbnormalProgramTermination(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* AbnormalProgramTermination::getPyExceptionType() const
{
    return PyExc_InterruptedError;
}

// ---------------------------------------------------------

UnknownProgramOption::UnknownProgramOption() = default;

UnknownProgramOption::UnknownProgramOption(const char* sMessage)
    : Exception(sMessage)
{}

UnknownProgramOption::UnknownProgramOption(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* UnknownProgramOption::getPyExceptionType() const
{
    return PyExc_FC_UnknownProgramOption;
}

// ---------------------------------------------------------

ProgramInformation::ProgramInformation() = default;

ProgramInformation::ProgramInformation(const char* sMessage)
    : Exception(sMessage)
{}

ProgramInformation::ProgramInformation(const std::string& sMessage)
    : Exception(sMessage)
{}

// ---------------------------------------------------------

TypeError::TypeError() = default;

TypeError::TypeError(const char* sMessage)
    : Exception(sMessage)
{}

TypeError::TypeError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* TypeError::getPyExceptionType() const
{
    return PyExc_TypeError;
}

// ---------------------------------------------------------

ValueError::ValueError() = default;

ValueError::ValueError(const char* sMessage)
    : Exception(sMessage)
{}

ValueError::ValueError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* ValueError::getPyExceptionType() const
{
    return PyExc_ValueError;
}

// ---------------------------------------------------------

IndexError::IndexError() = default;

IndexError::IndexError(const char* sMessage)
    : Exception(sMessage)
{}

IndexError::IndexError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* IndexError::getPyExceptionType() const
{
    return PyExc_IndexError;
}

// ---------------------------------------------------------

NameError::NameError() = default;

NameError::NameError(const char* sMessage)
    : Exception(sMessage)
{}

NameError::NameError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* NameError::getPyExceptionType() const
{
    return PyExc_NameError;
}

// ---------------------------------------------------------

ImportError::ImportError() = default;

ImportError::ImportError(const char* sMessage)
    : Exception(sMessage)
{}

ImportError::ImportError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* ImportError::getPyExceptionType() const
{
    return PyExc_ImportError;
}

// ---------------------------------------------------------

AttributeError::AttributeError() = default;

AttributeError::AttributeError(const char* sMessage)
    : Exception(sMessage)
{}

AttributeError::AttributeError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* AttributeError::getPyExceptionType() const
{
    return PyExc_AttributeError;
}

// ---------------------------------------------------------

RuntimeError::RuntimeError() = default;

RuntimeError::RuntimeError(const char* sMessage)
    : Exception(sMessage)
{}

RuntimeError::RuntimeError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* RuntimeError::getPyExceptionType() const
{
    return PyExc_RuntimeError;
}

// ---------------------------------------------------------

BadGraphError::BadGraphError()
    : RuntimeError("The graph must be a DAG.")
{}

BadGraphError::BadGraphError(const char* sMessage)
    : RuntimeError(sMessage)
{}

BadGraphError::BadGraphError(const std::string& sMessage)
    : RuntimeError(sMessage)
{}

PyObject* BadGraphError::getPyExceptionType() const
{
    return PyExc_FC_BadGraphError;
}

// ---------------------------------------------------------

NotImplementedError::NotImplementedError() = default;

NotImplementedError::NotImplementedError(const char* sMessage)
    : Exception(sMessage)
{}

NotImplementedError::NotImplementedError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* NotImplementedError::getPyExceptionType() const
{
    return PyExc_NotImplementedError;
}

// ---------------------------------------------------------

ZeroDivisionError::ZeroDivisionError() = default;

ZeroDivisionError::ZeroDivisionError(const char* sMessage)
    : Exception(sMessage)
{}

ZeroDivisionError::ZeroDivisionError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* ZeroDivisionError::getPyExceptionType() const
{
    return PyExc_ZeroDivisionError;
}

// ---------------------------------------------------------

ReferenceError::ReferenceError() = default;

ReferenceError::ReferenceError(const char* sMessage)
    : Exception(sMessage)
{}

ReferenceError::ReferenceError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* ReferenceError::getPyExceptionType() const
{
    return PyExc_ReferenceError;
}

// ---------------------------------------------------------

ExpressionError::ExpressionError() = default;

ExpressionError::ExpressionError(const char* sMessage)
    : Exception(sMessage)
{}

ExpressionError::ExpressionError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* ExpressionError::getPyExceptionType() const
{
    return PyExc_FC_ExpressionError;
}

// ---------------------------------------------------------

ParserError::ParserError() = default;

ParserError::ParserError(const char* sMessage)
    : Exception(sMessage)
{}

ParserError::ParserError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* ParserError::getPyExceptionType() const
{
    return PyExc_FC_ParserError;
}

// ---------------------------------------------------------

UnicodeError::UnicodeError() = default;

UnicodeError::UnicodeError(const char* sMessage)
    : Exception(sMessage)
{}

UnicodeError::UnicodeError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* UnicodeError::getPyExceptionType() const
{
    return PyExc_UnicodeError;
}

// ---------------------------------------------------------

OverflowError::OverflowError() = default;

OverflowError::OverflowError(const char* sMessage)
    : Exception(sMessage)
{}

OverflowError::OverflowError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* OverflowError::getPyExceptionType() const
{
    return PyExc_OverflowError;
}

// ---------------------------------------------------------

UnderflowError::UnderflowError() = default;

UnderflowError::UnderflowError(const char* sMessage)
    : Exception(sMessage)
{}

UnderflowError::UnderflowError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* UnderflowError::getPyExceptionType() const
{
    return PyExc_ArithmeticError;
}

// ---------------------------------------------------------

UnitsMismatchError::UnitsMismatchError() = default;

UnitsMismatchError::UnitsMismatchError(const char* sMessage)
    : Exception(sMessage)
{}

UnitsMismatchError::UnitsMismatchError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* UnitsMismatchError::getPyExceptionType() const
{
    return PyExc_ArithmeticError;
}

// ---------------------------------------------------------

CADKernelError::CADKernelError() = default;

CADKernelError::CADKernelError(const char* sMessage)
    : Exception(sMessage)
{}

CADKernelError::CADKernelError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* CADKernelError::getPyExceptionType() const
{
    return PyExc_FC_CADKernelError;
}

// ---------------------------------------------------------

RestoreError::RestoreError() = default;

RestoreError::RestoreError(const char* sMessage)
    : Exception(sMessage)
{}

RestoreError::RestoreError(const std::string& sMessage)
    : Exception(sMessage)
{}

PyObject* RestoreError::getPyExceptionType() const
{
    return PyExc_IOError;
}

// ---------------------------------------------------------

#if defined(__GNUC__) && defined(FC_OS_LINUX)
#include <stdexcept>
#include <iostream>

SignalException::SignalException()
{
    memset(&new_action, 0, sizeof(new_action));
    new_action.sa_handler = throw_signal;
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;
    ok = (sigaction(SIGSEGV, &new_action, &old_action) < 0);
#ifdef _DEBUG
    std::cout << "Set new signal handler" << std::endl;
#endif
}

SignalException::~SignalException()
{
    sigaction(SIGSEGV, &old_action, nullptr);
#ifdef _DEBUG
    std::cout << "Restore old signal handler" << std::endl;
#endif
}

void SignalException::throw_signal(int signum)
{
    std::cerr << "SIGSEGV signal raised: " << signum << std::endl;
    throw std::runtime_error("throw_signal");
}
#endif
